package agendaDOM_JAXB;

/**
 * It is responsible for implementing methods of Agenda with DOM.
 * @author <A HREF="fernandezlopez.raul@gmail.com">Ra�l Fern�ndez L�pez</A> 
 * @version 1.00
 */

import agendaInterfaz.MainWindow;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.swing.DefaultListModel;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;


/**
 * Class representing a list of contacts that can be read and written into and from an xml file.
 * 
 * This version uses DOM to process the xml.
 * 
 */
public class AgendaDOM implements Agenda{   
    
    private static final String XML_INPUT_PATH = "src/main/resources/xml/Contactos.xml";
    private static final String XML_OUTPUT_PATH = "src/main/resources/xml/Contactos_new.xml";
    
    /**
     *  Attribute to keep a reference to the current XML in memory.
     */
    private Document doc;
    
    /**
     * Attribute to keep the main window.
     */
    private MainWindow win; 

    /**
     * Atribute to keep the instance of AgendaDOM.
     */
    private static AgendaDOM instance= null;
    
    /**
     * Atribute to keep a list of contacts.
     */
    private DefaultListModel<String> modelo;
       
    /**
     * Private constructor
     * This class should only be used as singleton
     */
    private AgendaDOM(){}
    
    /**
     * Returns a DOM based agenda
     * @return AgendaDOM
     */
    public static AgendaDOM getInstance(){
    	if(instance==null) instance = new AgendaDOM();
    	return instance;
    }
    

    /**
     * Shows a list of current contacts on console. (Only name and surnames).
     * @return modelo List of Contacts.
     */

    public DefaultListModel<String> listContacts() {
    	NodeList contacts;
    	modelo = new DefaultListModel<String>();
    	
    	if (doc != null
    			&& (contacts = doc.getElementsByTagName("Contactos")) != null
    			&& contacts.getLength() > 0){
			contacts = doc.getElementsByTagName("Contactos");
			//Vector<String> datos = new Vector<String>();
			for (int i=0; i< contacts.getLength(); i++) {
				Element contact = (Element)contacts.item(i);
				
				modelo.addElement((i + 1) + ". " 
                        + contact.getElementsByTagName("Apellidos").item(0).getTextContent() + ", " 
						+ contact.getElementsByTagName("Nombre").item(0).getTextContent());
			}
    	} else {
            //System.out.println("(lista vacia)");
    		modelo.addElement("");
    		
        }
    	return modelo;
    }
    
    /**
     * Prints detailed information of selected user on console.
     * 
     * @param index Position of selected contact.
     * @return a string with details of a contact.
     * @throws IOException
     */

    public String printContactDetail(int index) throws IOException {
		NodeList contacts;
		StringBuilder sb=null;
		if (doc != null
				&& ((contacts = doc.getElementsByTagName("Contactos")) != null)
				&& contacts.getLength() >= index && index > 0) {
			Element cont = (Element) contacts.item(index - 1);
			sb = new StringBuilder("Detalle del contacto " + index + ": ");
			if (cont.getElementsByTagName("IdContacto") != null) {
				sb.append("\n\tID: ").append(cont.getElementsByTagName("IdContacto").item(0).getTextContent());
			}
			sb.append("\n\tNombre: ");
			if (cont.getElementsByTagName("Estimado").item(0) != null) {
				sb.append(cont.getElementsByTagName("Estimado").item(0).getTextContent());
			}
			if (cont.getElementsByTagName("Nombre").item(0) != null) {
				sb.append(cont.getElementsByTagName("Nombre").item(0).getTextContent()).append(" ");
			}
			if (cont.getElementsByTagName("Apellidos").item(0) != null) {
				sb.append(cont.getElementsByTagName("Apellidos").item(0).getTextContent());
			}
			if (cont.getElementsByTagName("Direccion").item(0) != null) {
				sb.append("\n\tDireccion: ").append(cont.getElementsByTagName("Direccion").item(0).getTextContent());
			}
			if (cont.getElementsByTagName("CodPostal").item(0) != null) {
				sb.append(", ").append(cont.getElementsByTagName("CodPostal").item(0).getTextContent());
			}
			if (cont.getElementsByTagName("Ciudad").item(0) != null) {
				sb.append(", ").append(cont.getElementsByTagName("Ciudad").item(0).getTextContent());
			}
			if (cont.getElementsByTagName("EdoOProv").item(0) != null) {
				sb.append(", ").append(cont.getElementsByTagName("EdoOProv").item(0).getTextContent());
			}
			if (cont.getElementsByTagName("Pais").item(0) != null) {
				sb.append(", ").append(cont.getElementsByTagName("Pais").item(0).getTextContent());
			}
			if (cont.getElementsByTagName("Cargo").item(0) != null) {
				sb.append("\n\tCargo: ").append(cont.getElementsByTagName("Cargo").item(0).getTextContent());
			}
			if (cont.getElementsByTagName("TelefonoTrabajo").item(0) != null) {
				sb.append("\n\tTfno. trabajo: ").append(cont.getElementsByTagName("TelefonoTrabajo").item(0).getTextContent());
			}
			if (cont.getElementsByTagName("ExtensionTrabajo").item(0) != null) {
				sb.append("\n\tExt. trabajo: ").append(cont.getElementsByTagName("ExtensionTrabajo").item(0).getTextContent());
			}
			if (cont.getElementsByTagName("NumFax").item(0) != null) {
				sb.append("\n\tFax: ").append(cont.getElementsByTagName("NumFax").item(0).getTextContent());
			}
			if (cont.getElementsByTagName("NomCorreoElectronico").item(0) != null) {
				sb.append("\n\tEmail: ").append(cont.getElementsByTagName("NomCorreoElectronico").item(0).getTextContent());
			}
    	} else {
    		System.out.println("No existe en la lista de contactos.");
        }
		return sb.substring(0);
    }

    /**
     * Creates a new contact and adds it to the XML representation in memory.
     * 
     * @throws IOException
     */

    public void createNewContact() throws IOException {
    
        if (doc == null){
        	DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        	try {
				doc = factory.newDocumentBuilder().newDocument();
				Element dataroot = doc.createElement("dataroot");
				SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
				String date = sdf.format(new Date());
				dataroot.setAttribute("generated", date);
				doc.appendChild(dataroot);
			} catch (ParserConfigurationException e) {
				System.err.println("Error al crear el documento XML.");
			}
        }
        
        Node dataroot = doc.getElementsByTagName("dataroot").item(0);
        
        Element contact = doc.createElement("Contactos");
        
        win = new MainWindow();
        
        
        Element contactId = doc.createElement("IdContacto");
        contactId.appendChild(doc.createTextNode(String.valueOf(doc.getElementsByTagName("Contactos").getLength() + 1)));
        contact.appendChild(contactId);
        
        Element contactSurnames = doc.createElement("Apellidos");
        contactSurnames.appendChild(doc.createTextNode(win.getFrame().getTxtApellido().getText()));
        contact.appendChild(contactSurnames);
        
        Element contactName = doc.createElement("Nombre");
        contactName.appendChild(doc.createTextNode(win.getFrame().getTxtNombre().getText()));
        contact.appendChild(contactName);
        
        Element contactPhone = doc.createElement("TelefonoTrabajo");
        contactName.appendChild(doc.createTextNode(win.getFrame().getTxtTelefono().getText()));
        contact.appendChild(contactPhone);
        
        Element contactMail = doc.createElement("nomCorreoElectronico");
        contactName.appendChild(doc.createTextNode(win.getFrame().getTxtMail().getText()));
        contact.appendChild(contactMail);

        dataroot.appendChild(contact);
    }

    /**
     * Method for editing the information for the selected contact in the XML representation in memory.
     * 
     * @param index Position of selected contact.
     * @return true if the contact is change correctly.
     * @throws IOException
     */

        public boolean editContact(int index) throws IOException {
    	NodeList contacts;
    	win = new MainWindow();
    	boolean encontrado = false;
    	
    	if (doc != null
    		&& ((contacts = doc.getElementsByTagName("Contactos")) != null)
    		&& contacts.getLength() >= index && index > 0){
    			for (int i=0; i< contacts.getLength() && !encontrado; i++) {
    				Element contact = (Element)contacts.item(i);
    				if (index == Integer.valueOf(contact.getElementsByTagName("IdContacto").item(0).getTextContent()) ){
    					contact.getElementsByTagName("Nombre").item(0).setTextContent(win.getFrame().getTxtNombre().getText());
    					contact.getElementsByTagName("Apellidos").item(0).setTextContent(win.getFrame().getTxtApellido().getText());
    					contact.getElementsByTagName("TelefonoTrabajo").item(0).setTextContent(win.getFrame().getTxtTelefono().getText());
    					contact.getElementsByTagName("nomCorreoElectronico").item(0).setTextContent(win.getFrame().getTxtMail().getText());	
    					return true;
    				}
    			}
    		}
    	return false;
    }

    /**
     * Removes the selected contact from the XML representation in memory.
     * 
     * @param index Position of selected contact.
     */

    public void deleteContact(int index) {
    	NodeList contacts;
    	if (doc != null
    			&& ((contacts = doc.getElementsByTagName("Contactos")) != null)
    			&& contacts.getLength() >= index && index > 0){
    		Element cont = (Element)contacts.item(index - 1);
    		cont.getParentNode().removeChild(cont);
    		System.out.println("Contacto borrado.");
    	} else {
          System.out.println("No existe en la lista de contactos.");
    	}
    }
    
    /**
     * Writes the current XML in a file.
     * 
     * @return message after save a contact.
     */

    public String saveXML() {
    	String message="";
    	
    	if (doc != null) {
	    	TransformerFactory transformerFactory = TransformerFactory.newInstance();
			Transformer transformer;
			try {
				transformer = transformerFactory.newTransformer();
				DOMSource source = new DOMSource(doc);
				StreamResult result = new StreamResult(new File(XML_OUTPUT_PATH));
				transformer.transform(source, result);
				message="Se ha guardado correctamente el fichero " + XML_OUTPUT_PATH;
			} catch (TransformerConfigurationException e) {
				message="Error guardando el fichero XML: " + e;
				
			} catch (TransformerException e) {
				message="Error guardando el fichero XML." + e;
				
			}
    	} else {
	    	message="Aun no existe ningun contacto";
	    }
    	return message;
    }

    /**
     * Reads the input XML file.
     */

    public void loadXML() {
    	// Create a DOM builder and parse the fragment
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        try {
			doc = factory.newDocumentBuilder().parse(new File(XML_INPUT_PATH));
			//doc = factory.newDocumentBuilder().parse(new File(XML_OUTPUT_PATH));
        	System.out.println("Se ha leido el fichero " + XML_INPUT_PATH);
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		}
    }
        
    /**
     * Search contacts with the given name in the XML in memory.
     * 
     * Uses XPath for the search.
     *  
     * @param name Name of the contact to search.
     * @return list that contains the contacts of search by name. 
     */

    public List<String> searchByName(String name) {
    	String predicate = "Nombre='" + name + "'";
    	return xpathSearch(predicate);
    }
    
    /**
     * Search contacts with the given job in the XML in memory.
     * 
     * Uses XPath for the search.
     *  
     * @param job Job of the contact to search.
     * @return list that contains the contacts of search by job. 
     */

    public List<String> searchByJob(String job) {
    	String predicate = "Cargo='" + job + "'";
   System.out.println("AUPA:"+xpathSearch(predicate).size()+":");
    	return xpathSearch(predicate);
    }
    	
    /**
     * Search contacts with the given postal code in the XML in memory.
     * 
     * Uses XPath for the search.
     *  
     * @param postalCode Postal code of the contact to search.
     * @return list that contains the contacts of search by postal code. 
     */

    public List<String> searchByPostalCode(String postalCode) {
    	String predicate = "CodPostal='" + postalCode + "'";
    	return xpathSearch(predicate);
    }

    /**
     * Prepares a query to evaluate with XPath and executes the query.
     * 
     * @param predicate The predicate specifying the specific search to do.
     * @return list that contains the contacts of search by predicate. 
     */
 
    public List<String> xpathSearch(String predicate) {
    	List<String> contactos=new ArrayList<String>();
    	if (doc != null) {
	    	List<String> resultNames = 
	    			evaluateXPathQuery("//Contactos[" + predicate + "]/Nombre/text()");
	    	List<String> resultSurnames = 
	    			evaluateXPathQuery("//Contactos[" + predicate + "]/Apellidos/text()");
	    	List<String> resultID =
	    			evaluateXPathQuery("//Contactos[" + predicate + "]/IdContacto/text()");
	    	if (resultNames != null && !resultNames.isEmpty()){
		    	System.out.println("Encontrados " + resultNames.size() + " resultados: ");
		    	for (int i=0; i<resultNames.size(); i++ ) {
					contactos.add(resultID.get(i) + "- " + resultNames.get(i) + " " +resultSurnames.get(i));
				}
	    	} else {
	    		contactos.add("");
	    	}
    	} else {
    		contactos.add("");
    		System.out.println("Aun no hay contactos cargados");
    	}
    	return contactos;
    }

    /**
     * Performs the given XPath query against the XML in memory.
     * 
     * @param query XPath query.
     * @return list that save the contacts of the query. 
     */
    private List<String> evaluateXPathQuery(String query) {
    	List<String> results = null;
        XPathFactory factory = null;
        XPath xpath = null;
    	if (doc != null){
	    	results = new ArrayList<String>();
	        factory = XPathFactory.newInstance();
	        xpath = factory.newXPath();
	        try {
	            XPathExpression expr = xpath.compile(query);
	            Object result = expr.evaluate(doc, XPathConstants.NODESET); 
	            NodeList nodes = (NodeList) result; 
	            for (int i = 0; i < nodes.getLength(); i++) {
	                results.add(nodes.item(i).getNodeValue()); 
	            }
	        } catch (XPathExpressionException ex) {
	            System.err.println("Error al buscar el elemento: " + ex);
	        }
    	} 
        return results;
    }
    
    /**
     * Method that returns size of List of contacts.
     * 
     * @return size of List of contacts. 
     */

    public int getNumContactos(){
    	return modelo.size();
    }
    
    /**
     * Shows a list of current contacts on console. (Only name and surnames).
     * 
     * @param idContacto ID of a contact.
     */

    public void searchContactLostfocus(int idContacto) {
    	NodeList contacts;
    	win = new MainWindow();
    	boolean encontrado = false;
    	
    	if (doc != null
    			&& (contacts = doc.getElementsByTagName("Contactos")) != null
    			&& contacts.getLength() > 0){
			contacts = doc.getElementsByTagName("Contactos");

			for (int i=0; i< contacts.getLength() && !encontrado; i++) {
				Element contact = (Element)contacts.item(i);
				if (idContacto == Integer.valueOf(contact.getElementsByTagName("IdContacto").item(0).getTextContent())){
					win.getFrame().getTxtNombre().setText(contact.getElementsByTagName("Nombre").item(0).getTextContent());
					win.getFrame().getTxtApellido().setText(contact.getElementsByTagName("Apellidos").item(0).getTextContent());
					win.getFrame().getTxtTelefono().setText(contact.getElementsByTagName("TelefonoTrabajo").item(0).getTextContent());
					win.getFrame().getTxtMail().setText(contact.getElementsByTagName("nomCorreoElectronico").item(0).getTextContent());
					encontrado=true;
				}
			}
			if (!encontrado){
				win.getFrame().getTxtNombre().setText("");
				win.getFrame().getTxtApellido().setText("");
				win.getFrame().getTxtTelefono().setText("");
				win.getFrame().getTxtMail().setText("");
			}

    	} 
    }
}